// Copyright (c) 2017 Presto Labs Pte. Ltd.
// Author: jhkim

#pragma once

#include <algorithm>
#include <utility>

namespace coin2::exchange::sim::order_v1 {

const double kNoExploitMarketOrderBuffer = 10e-4;

template<typename LightBook, typename PriceType>
std::pair<double, double> GetBuyAggFillAtPrice(
    const LightBook& level_book,
    PriceType price,
    double order_qty) {
  double fill_pq_sum = 0;
  double fill_qty_sum = 0;
  int i = 0;
  for (i = 0; i < level_book.num_ask_levels(); ++i) {
    if (order_qty <= 0) break;
    if (price >= level_book.ask_price(i)) {
      double level_qty = level_book.ask_qty(i);
      if (level_qty == 0) continue;
      double filled_qty = std::min(level_qty, order_qty);
      fill_qty_sum += filled_qty;
      fill_pq_sum += filled_qty * static_cast<double>(level_book.ask_price(i));
      order_qty -= filled_qty;
    } else {
      break;
    }
  }
  if (i == level_book.num_ask_levels() && order_qty > 0) {
    CHECK_GE(price, level_book.ask_price(i - 1));
    fill_qty_sum += order_qty;
    double bad_buy_price = static_cast<double>(
        level_book.ask_price(i - 1)) * (1 + kNoExploitMarketOrderBuffer);
    fill_pq_sum += order_qty * std::min(static_cast<double>(price), bad_buy_price);
    order_qty = 0;
  }
  if (fill_qty_sum == 0) return std::make_pair(0, 0);
  return std::make_pair(fill_pq_sum / fill_qty_sum, fill_qty_sum);
}

template<typename LightBook, typename PriceType>
std::pair<double, double> GetSellAggFillAtPrice(
    const LightBook& level_book,
    PriceType price,
    double order_qty) {
  double fill_pq_sum = 0;
  double fill_qty_sum = 0;
  int i = 0;
  for (i = 0; i < level_book.num_bid_levels(); ++i) {
    if (order_qty <= 0) break;
    if (price <= level_book.bid_price(i)) {
      double level_qty = level_book.bid_qty(i);
      if (level_qty == 0) continue;
      double filled_qty = std::min(level_qty, order_qty);
      fill_qty_sum += filled_qty;
      fill_pq_sum += filled_qty * static_cast<double>(level_book.bid_price(i));
      order_qty -= filled_qty;
    } else {
      break;
    }
  }
  if (i == level_book.num_bid_levels() && order_qty > 0) {
    CHECK_LE(price, level_book.bid_price(i - 1));
    fill_qty_sum += order_qty;
    double bad_sell_price = static_cast<double>(
        level_book.bid_price(i - 1)) * (1 - kNoExploitMarketOrderBuffer);
    fill_pq_sum += order_qty * std::max(static_cast<double>(price), bad_sell_price);
    order_qty = 0;
  }
  if (fill_qty_sum == 0) return std::make_pair(0, 0);
  return std::make_pair(fill_pq_sum / fill_qty_sum, fill_qty_sum);
}

template<typename LightBook, typename PriceType>
inline bool GetAskQtyAtPrice(
    const LightBook& level_book,
    PriceType price,
    double* qty) {
  CHECK(qty);
  for (int i = 0; i < level_book.num_ask_levels(); ++i) {
    if (price == level_book.ask_price(i)) {
      *qty = level_book.ask_qty(i);
      return true;
    } else if (price < level_book.ask_price(i)) {
      *qty = 0;
      return true;
    }
  }
  return false;
}

template<typename LightBook, typename PriceType>
inline bool GetBidQtyAtPrice(
    const LightBook& level_book,
    PriceType price,
    double* qty) {
  CHECK(qty);
  for (int i = 0; i < level_book.num_bid_levels(); ++i) {
    if (price == level_book.bid_price(i)) {
      *qty = level_book.bid_qty(i);
      return true;
    } else if (price > level_book.bid_price(i)) {
      *qty = 0;
      return true;
    }
  }
  return false;
}

}  // namespace coin2::exchange::sim::order_v1
